﻿using System.Diagnostics;
using System.Reflection;
using System.Text.RegularExpressions;
using FrameworkCore.Constants;
using FrameworkCore.Utilities;

namespace FrameworkCore.Modules;

#nullable disable

/// <summary>
/// 提供关于当前 Web 应用程序中类型的信息。
/// 可选择性地，此类可以查看 bin 文件夹中的所有程序集。
/// </summary>
public partial class WebAppTypeFinder : ITypeFinder
{
    #region Constants

    /// <summary>获取不需要检查的 DLL 模式。</summary>
    protected const string ASSEMBLY_SKIP_LOADING_PATTERN = "^System|^mscorlib|^Microsoft|^AjaxControlToolkit|^Antlr3|^Autofac|^AutoMapper|^Castle|^ComponentArt|^CppCodeProvider|^DotNetOpenAuth|^EntityFramework|^EPPlus|^FluentValidation|^ImageResizer|^itextsharp|^log4net|^MaxMind|^MbUnit|^MiniProfiler|^Mono.Math|^MvcContrib|^Newtonsoft|^NHibernate|^nunit|^Org.Mentalis|^PerlRegex|^QuickGraph|^Recaptcha|^Remotion|^RestSharp|^Rhino|^Telerik|^Iesi|^TestDriven|^TestFu|^UserAgentStringLibrary|^VJSharpCodeProvider|^WebActivator|^WebDev|^WebGrease";

    #endregion

    #region 字段

    protected static readonly Dictionary<string, Assembly> _assemblies = new(StringComparer.InvariantCultureIgnoreCase);

    protected static bool _loaded;
    protected static readonly object _locker = new();

    private readonly INetModulerFileProvider _fileProvider;

    #endregion

    #region Ctor

    public WebAppTypeFinder()
    {
        _fileProvider = CommonHelper.DefaultFileProvider;
    }

    static WebAppTypeFinder()
    {
        AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad;
    }

    #endregion

    #region Utilities

    private static void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args)
    {
        var assembly = args.LoadedAssembly;

        if (assembly.FullName == null)
            return;

        if (_assemblies.ContainsKey(assembly.FullName))
            return;

        if (!Matches(assembly.FullName))
            return;

        _assemblies.TryAdd(assembly.FullName, assembly);
    }

    /// <summary>
    /// 检查一个 DLL 是否是已知无需调查的预装 DLL。
    /// </summary>
    /// <param name="assemblyFullName">
    /// 要检查的程序集名称。
    /// </param>
    /// <returns>
    /// 如果应加载该程序集则为 true。
    /// </returns>
    protected static bool Matches(string assemblyFullName)
    {
        return !Regex.IsMatch(assemblyFullName, ASSEMBLY_SKIP_LOADING_PATTERN, RegexOptions.IgnoreCase | RegexOptions.Compiled);
    }

    /// <summary>
    /// 检查类型是否实现了泛型接口。
    /// </summary>
    /// <param name="type">要检查的类型。</param>
    /// <param name="openGeneric">开放泛型类型。</param>
    /// <returns>如果类型实现了指定的泛型接口，则返回 true。</returns>
    protected virtual bool DoesTypeImplementOpenGeneric(Type type, Type openGeneric)
    {
        try
        {
            var genericTypeDefinition = openGeneric.GetGenericTypeDefinition();
            return type.FindInterfaces((_, _) => true, null)
                .Where(implementedInterface => implementedInterface.IsGenericType).Any(implementedInterface =>
                    genericTypeDefinition.IsAssignableFrom(implementedInterface.GetGenericTypeDefinition()));
        }
        catch
        {
            return false;
        }
    }

    /// <summary>
    /// 查找指定类型的类。
    /// </summary>
    /// <param name="assignTypeFrom">要分配类型的基类或接口。</param>
    /// <param name="assemblies">要搜索的程序集集合。</param>
    /// <param name="onlyConcreteClasses">一个值，指示是否仅查找具体的类，默认为 true。</param>
    /// <returns>符合条件的类型集合。</returns>
    protected virtual IEnumerable<Type> FindClassesOfType(Type assignTypeFrom, IEnumerable<Assembly> assemblies, bool onlyConcreteClasses = true)
    {
        var result = new List<Type>();

        try
        {
            foreach (var a in assemblies)
            {
                Type[] types = null;
                try
                {
                    types = a.GetTypes();
                }
                catch
                {
                    // 忽略异常
                }

                if (types == null)
                    continue;

                foreach (var t in types)
                {
                    if (!assignTypeFrom.IsAssignableFrom(t) && (!assignTypeFrom.IsGenericTypeDefinition || !DoesTypeImplementOpenGeneric(t, assignTypeFrom)))
                        continue;

                    if (t.IsInterface)
                        continue;

                    if (onlyConcreteClasses)
                    {
                        if (t.IsClass && !t.IsAbstract)
                            result.Add(t);
                    }
                    else
                        result.Add(t);
                }
            }
        }
        catch (ReflectionTypeLoadException ex)
        {
            var msg = string.Empty;

            if (ex.LoaderExceptions.Any())
                msg = ex.LoaderExceptions.Where(e => e != null)
                    .Aggregate(msg, (current, e) => $"{current}{e.Message + Environment.NewLine}");

            var fail = new Exception(msg, ex);
            Debug.WriteLine(fail.Message, fail);

            throw fail;
        }

        return result;
    }

    /// <summary>
    /// 初始化数据。
    /// </summary>
    protected virtual void InitData()
    {
        // 如果数据已经加载，则直接返回
        if (_loaded)
            return;

        // 防止多次加载数据
        lock (_locker)
        {
            // 再次检查是否已加载数据（防止在等待期间数据已加载）
            if (_loaded)
                return;

            // 遍历当前应用程序域中的所有程序集
            foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                // 如果程序集的完整名称为null，则跳过
                if (assembly.FullName == null)
                    continue;

                // 如果不符合指定的匹配条件，则跳过
                if (!Matches(assembly.FullName))
                    continue;

                // 将符合条件的程序集添加到_assemblies字典中
                _assemblies.TryAdd(assembly.FullName, assembly);
            }

            // 遍历需要加载程序集的目录
            foreach (var directoriesToLoadAssembly in DirectoriesToLoadAssemblies)
            {
                // 如果目录不存在，则跳过
                if (!_fileProvider.DirectoryExists(directoriesToLoadAssembly))
                    continue;

                // 遍历目录中的所有.dll文件
                foreach (var dllPath in _fileProvider.GetFiles(directoriesToLoadAssembly, GlobalConstants.AssemblySuffix))
                {
                    try
                    {
                        // 获取程序集的AssemblyName
                        var an = AssemblyName.GetAssemblyName(dllPath);

                        // 如果_assemblies字典中已包含该程序集的完整名称，则跳过
                        if (_assemblies.ContainsKey(an.FullName))
                            continue;

                        // 如果不符合指定的匹配条件，则跳过
                        if (!Matches(an.FullName))
                            continue;

                        Assembly assembly;

                        try
                        {
                            // 尝试加载程序集到当前应用程序域
                            assembly = AppDomain.CurrentDomain.Load(an);
                        }
                        catch
                        {
                            // 加载失败时，使用Assembly.LoadFrom方法加载程序集
                            assembly = Assembly.LoadFrom(dllPath);
                        }

                        // 将加载成功的程序集添加到_assemblies字典中
                        _assemblies.TryAdd(assembly.FullName, assembly);
                    }
                    catch (BadImageFormatException ex)
                    {
                        // 记录BadImageFormatException异常信息
                        Trace.TraceError(ex.ToString());
                    }
                }
            }

            // 标记数据已加载
            _loaded = true;
        }
    }

    #endregion

    #region 方法

    /// <summary>
    /// 获取与当前实现相关的程序集。
    /// </summary>
    /// <returns>程序集列表</returns>
    public virtual IList<Assembly> GetAssemblies()
    {
        if (!_loaded)
            InitData();

        return _assemblies.Values.ToList();
    }

    /// <summary>
    /// 查找特定类型的类。
    /// </summary>
    /// <typeparam name="T">类型</typeparam>
    /// <param name="onlyConcreteClasses">一个值，指示是否仅查找具体类</param>
    /// <returns>结果</returns>
    public virtual IEnumerable<Type> FindClassesOfType<T>(bool onlyConcreteClasses = true)
    {
        return FindClassesOfType(typeof(T), onlyConcreteClasses);
    }

    /// <summary>
    /// 查找特定类型的类。
    /// </summary>
    /// <param name="assignTypeFrom">指定的类型</param>
    /// <param name="onlyConcreteClasses">一个值，指示是否仅查找具体类</param>
    /// <returns>结果</returns>
    public virtual IEnumerable<Type> FindClassesOfType(Type assignTypeFrom, bool onlyConcreteClasses = true)
    {
        return FindClassesOfType(assignTypeFrom, GetAssemblies(), onlyConcreteClasses);
    }

    /// <summary>
    /// 根据完整名称获取程序集。
    /// </summary>
    /// <param name="assemblyFullName">程序集的完整名称</param>
    /// <returns>程序集</returns>
    public virtual Assembly GetAssemblyByName(string assemblyFullName)
    {
        if (!_loaded)
            InitData();

        _assemblies.TryGetValue(assemblyFullName, out var assembly);

        if (assembly != null)
            return assembly;

        var assemblyName = new AssemblyName(assemblyFullName);
        var key = _assemblies.Keys.FirstOrDefault(k => k.StartsWith(assemblyName.Name ?? assemblyFullName.Split(' ')[0], StringComparison.InvariantCultureIgnoreCase));

        return string.IsNullOrEmpty(key) ? null : _assemblies[key];
    }

    #endregion

    #region  属性

    /// <summary>
    /// 获取或设置初始化时要加载的程序集目录列表。
    /// </summary>
    /// <remarks>
    /// 例如，Web 应用程序的 bin 文件夹应在应用程序加载时进行特定检查。这在应用程序重新加载后需要在 AppDomain 中加载插件的情况下是必需的。
    /// </remarks>
    public virtual List<string> DirectoriesToLoadAssemblies { get; set; } = new()
    {
        AppContext.BaseDirectory
    };

    #endregion
}